package se.thoka.twt.client.utils;

import se.thoka.twt.client.event.CountDownEvent;
import se.thoka.twt.client.event.CountDownHandler;
import se.thoka.twt.client.event.HasCountDownHandlers;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.testing.CountingEventBus;
import com.google.gwt.user.client.Timer;

public class CountDownTimer implements HasCountDownHandlers {
	
	private static final int SECOND_IN_MILLIS = 1000;
	private static final int MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
	private static final int HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
	
	public enum RefreshInterval {
		ONCE_EVERY_HOUR(HOUR_IN_MILLIS),
		ONCE_EVERY_MINUTE(MINUTE_IN_MILLIS),
		ONCE_EVERY_SECOND(SECOND_IN_MILLIS),
		DEFAULT(SECOND_IN_MILLIS),
		REALTIME(SECOND_IN_MILLIS / 25), // 25 FPS
		MINIMUM(REALTIME.interval);
		
		private int interval;
		private RefreshInterval(int interval) {
			this.interval = interval;
		}
	}
	
	private CountingEventBus eventBus;
	private long startTime = -1;
	private final long endTime;
	private final int refreshInterval;
	private Timer timer;
	private boolean running;

	public CountDownTimer(GWTDateTime endTime) {
		this(endTime.toDate().getTime());
	}
	public CountDownTimer(GWTDateTime endTime, RefreshInterval interval) {
		this(endTime.toDate().getTime(), interval);
	}
	public CountDownTimer(GWTDateTime endTime, int interval) {
		this(endTime.toDate().getTime(), interval);
	}
	public CountDownTimer(long endTime) {
		this(endTime, RefreshInterval.DEFAULT);
	}
	public CountDownTimer(long endTime, RefreshInterval interval) {
		this(endTime, interval.interval);
	}
	public CountDownTimer(long endTime, int refreshInterval) {
		if (endTime <= System.currentTimeMillis()) {
			throw new IllegalArgumentException("End time must be in the future");
		}
		
		this.endTime = endTime;
		this.refreshInterval = Math.max(refreshInterval, RefreshInterval.MINIMUM.interval);
		this.eventBus = new CountingEventBus();
		this.timer = new Timer() {
			@Override
			public void run() {
				refresh();
			}
		};
	}

	@Override
	public HandlerRegistration addCountDownHandler(CountDownHandler handler) {
		HandlerRegistration registration = eventBus.addHandler(CountDownEvent.TYPE, handler);
		Scheduler.get().scheduleDeferred(new ScheduledCommand() {
			@Override
			public void execute() {
				if (hasStarted() && !isRunning() && !isComplete()) {
					start();
				}
			}
		});
		
		return registration;
	}
	
	public long getTimeLeft() {
		return endTime - System.currentTimeMillis();
	}
	
	public boolean hasStarted() {
		return startTime != -1;
	}
	
	public boolean isComplete() {
		return getTimeLeft() <= 0;
	}
	
	public boolean isRunning() {
		return running;
	}
	
	public void start() {
		if (!hasStarted()) {
			startTime = System.currentTimeMillis();
		}
		
		if (eventBus.getCount(CountDownEvent.TYPE) > 0) {
			timer.run();
			timer.scheduleRepeating(refreshInterval);
			running = true;
		}
	}

	@Override
	public void fireEvent(GwtEvent<?> event) {
		eventBus.fireEvent(event);
	}
	
	public void stop() {
		timer.cancel();
		running = false;
	}
	
	private void refresh() {
		long timeLeft = getTimeLeft();
		if (timeLeft <= 0 || eventBus.getCount(CountDownEvent.TYPE) == 0) {
			stop();
		}
		
		fireEvent(new CountDownEvent(timeLeft, endTime - startTime));
	}
}
